﻿using System.Collections.Concurrent;
using System.Diagnostics;
using System.Net.Sockets;
using System.Text;
using SecsGemLib.Base;

namespace SecsGemLib
{
    public class HSMS
    {
        private Dictionary<byte, string> Errors = new Dictionary<byte, string>()
        {
            {0x00,"Abort Transaction" },
            {0x01,"Unrecognized Device ID" },
            {0x03,"Unrecognized Stream Type" },
            {0x05,"Unrecognized Function Type" },
            {0x07,"Illegal Format" },
            {0x09,"Transaction Timer Timeout" },
        };

        // 主动发送请求的等待响应超时时间
        public int T3 { get; set; } = 45_000;
        // 主动发送请求的时间间隔
        public int T4 { get; set; } = 10_000;
        // 连接间隔时间，表示两个连接请求之间的时间间隔，指断开连接和重新连接的最小时间(一次连接失败后，需等待T5时间后重新连接)
        public int T5 { get; set; } = 10_000;
        // 控制会话超时，表示一个控制回话所能开启的最长时间，发送req消息时T6开启，如果未在T6超时之前收到rsp消息，则断开连接
        public int T6 { get; set; } = 15_000;
        // Select状态超时，建立了socket连接后，必须于T7时间内完成Select操作，否则断开连接
        public int T7 { get; set; } = 10_000;
        // 网络字符超时，表示成功接收到单个HSMS 消息的字符之间的最大时间间隔
        public int T8 { get; set; } = 5_000;
        public int BeatInterval { get; set; } = 15_000;

        CancellationTokenSource cts = new CancellationTokenSource();

        ConcurrentDictionary<string, MessageModel> _messageDic = new ConcurrentDictionary<string, MessageModel>();

        // 设备型号标识
        public string MDLN { get; set; }

        // 设备软件版本号
        public string SOFTREV { get; set; }

        private byte[] _deviceId;
        private Socket _socket;
        Random random = new Random();
        public HSMS()
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        }

        public void Connect(string Ip, int port)
        {
            _socket.Connect(Ip, port);
            this.Response();

            byte[] system_bytes = new byte[]
            {
                (byte)random.Next(0,255),
                (byte)random.Next(0,255),
                (byte)random.Next(0,255),
                (byte)random.Next(0,255)
            };

            // 发送Select状态请求
            byte[] req_bytes = new byte[]
            {
                0x00,0x00,0x00,0x0A,
                0x00,0x00,
                0x00,0x00,
                0x00,0x01,
                system_bytes[0],
                system_bytes[1],
                system_bytes[2],
                system_bytes[3]
            };

            _socket.Send(req_bytes);
            byte[] resp_bytes = this.GetReply(string.Join("", system_bytes.Select(b => b.ToString("X2"))), T6);
            if (resp_bytes[5] != 2)
                throw new Exception("响应异常");
            _deviceId = resp_bytes.Take(2).ToArray();

            this.LinkTest();
        }

        // 心跳
        private void LinkTest()
        {
            Task.Run(async () =>
            {
                await Task.Delay(BeatInterval);
                byte[] system_bytes = new byte[]
                {
                    (byte)random.Next(0,255),
                    (byte)random.Next(0,255),
                    (byte)random.Next(0,255),
                    (byte)random.Next(0,255)
                };
                byte[] link_bytes = new byte[]
                {
                    0x00,0x00,0x00,0x0A,
                    0xFF,0XFF,
                    0x00,0x00,
                    0x00,0x05,
                    system_bytes[0],
                    system_bytes[1],
                    system_bytes[2],
                    system_bytes[3]
                };
                Console.WriteLine(string.Join(" ",link_bytes.Select(b=>b.ToString("X2"))));
                _socket.Send(link_bytes);
            }, cts.Token);
        }

        /// <summary>
        /// 获取请求回复消息
        /// </summary>
        /// <param name="system_bytes"></param>
        /// <param name="t"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        private byte[] GetReply(string system_bytes, int t)
        {
            byte[] resp = null;
            Task task = Task.Run(() =>
            {
                MessageModel msg;
                while (!cts.IsCancellationRequested)
                {
                    if (_messageDic.TryGetValue(system_bytes, out msg))
                    {
                        resp = msg.MessageBytes;
                        break;
                    }
                }
            }, cts.Token);
            bool state = task.Wait(t);
            if (!state)
                // 说明超时了
                throw new Exception("接收响应超时了");

            if (resp == null)
                throw new Exception("响应异常");

            if (resp[2] == 9)
            {
                if (Errors.ContainsKey(resp[3]))
                    throw new Exception(Errors[resp[3]]);
                else
                    throw new Exception("响应异常");
            }

            return resp;
        }

        /// <summary>
        /// For active request and passive response to equipment
        /// </summary>
        /// <param name="deviceId"></param>
        /// <param name="stream"></param>
        /// <param name="function"></param>
        /// <param name="arg"></param>
        /// <param name="system_bytes"></param>
        /// <param name="isReply"></param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public Message Request(byte[] deviceId,
            byte stream,
            byte function,
            Message arg,
            byte[] system_bytes = null, bool isReply = false)
        {
            if (!isReply)
            {
                system_bytes = new byte[]
                {
                    (byte)random.Next(0,255),
                    (byte)random.Next(0,255),
                    (byte)random.Next(0,255),
                    (byte)random.Next(0,255)
                };
            }

            List<byte> req_head = new List<byte>()
            {
                0x00,0x00,0x00,0x0A,
                deviceId[0],deviceId[1],
                stream,function,
                0x00,0x00
            };
            req_head.AddRange(system_bytes);
            if (arg != null)
            {
                byte[] req_body = MessageToBytes(arg);
                int len = req_body.Length + 10;
                byte[] len_bytes = BitConverter.GetBytes(len);
                if (BitConverter.IsLittleEndian)
                    Array.Reverse(len_bytes);
                for (int i = 0; i < len_bytes.Length; i++)
                {
                    req_head[i] = len_bytes[i];
                }
                req_head.AddRange(req_body);
            }

            Console.WriteLine(string.Join(" ", req_head.Select(s => s.ToString("X2"))));
            _socket.Send(req_head.ToArray(), SocketFlags.None);
            // 如果是被动响应回复，这里不需要做响应接收，直接发送响应结果到对方
            if (isReply) return null;
            byte[] resp = GetReply(string.Join("", system_bytes.Select(s => s.ToString("X2"))), T6);

            // response function = original function + 1
            if ((resp[2] | 0x80) != stream || resp[3] != (function + 1))
            {
                if (resp[2] == 0x09)
                    if (Errors.ContainsKey(resp[3]))
                        throw new Exception(Errors[resp[3]]);
                    else
                        throw new Exception("响应异常");
            }
            byte[] resp_body = resp.Skip(10).ToArray();
            return GetMessage(resp_body);
        }

        /// <summary>
        /// 接收请求响应以及对主动请求进行响应回复
        /// </summary>
        private void Response()
        {
            Task.Run(() =>
            {
                while (!cts.IsCancellationRequested)
                {
                    byte[] len_bytes = new byte[4];
                    int count = _socket.Receive(len_bytes, 0, len_bytes.Length, SocketFlags.None);
                    if (count <= 0) continue;

                    int total = 0;
                    if (BitConverter.IsLittleEndian)
                        Array.Reverse(len_bytes);
                    total = BitConverter.ToInt32(len_bytes);

                    byte[] resp_head = new byte[10];
                    count = _socket.Receive(resp_head, 0, resp_head.Length, SocketFlags.None);
                    if (count <= 0) continue;

                    byte[] resp_body = new byte[total - 10];
                    if(total - 10 != 0)
                        _socket.Receive(resp_body, 0, resp_body.Length, SocketFlags.None);

                    byte[] system_bytes = resp_head.Skip(6).ToArray();
                    if (resp_head[3] % 2 == 0 || (resp_head[2] == 0 && resp_head[3] == 0))
                    {
                        List<byte> msg_bytes = new List<byte>();
                        msg_bytes.AddRange(resp_head);
                        msg_bytes.AddRange(resp_body);
                        MessageModel model = new MessageModel()
                        {
                            SystemBytes = system_bytes,
                            MessageBytes = msg_bytes.ToArray(),
                            RecTime = DateTime.Now,
                        };
                        _messageDic.TryAdd(string.Join("", system_bytes.Select(b => b.ToString("X2"))), model);
                        Console.WriteLine(string.Join(" ", msg_bytes.Select(b => b.ToString("X2"))));
                    }
                    else
                    {
                        byte stream = resp_head[2];
                        byte function = (byte)(resp_head[3] + 1);
                        if (stream < 0x80)
                        {

                        }
                        else
                        {
                            stream &= 0b01111111;   // 最高位置 0，其他位置值不变
                            byte[] sb = system_bytes;
                            System.Reflection.MethodInfo? method = this.GetType()
                                .GetMethod($"S{stream}F{function}", 
                                System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance);
                            if (method == null)
                                throw new Exception("相关功能不支持");
                            Message msg = method.Invoke(this, null) as Message;
                            this.Request(resp_head.Take(2).ToArray(), stream, function, msg, sb, true);
                        }
                    }
                }
            }, cts.Token);
        }

        private byte[] MessageToBytes(Message arg)
        {
            List<byte> req_bytes = new List<byte>();
            byte byte_len = (byte)Math.Ceiling(arg.Length * 1.0 / 256);
            if (byte_len > 3)
                throw new Exception("数据长度异常");
            byte type_len = (byte)((byte)arg.DataType | byte_len);
            req_bytes.Add(type_len);

            byte[] data_len = BitConverter.GetBytes(arg.Length);    // 得到的是一个四字节的数据
            if (BitConverter.IsLittleEndian)
                Array.Reverse(data_len);

            for (int i = 4 - byte_len; i < 4; i++)
            {
                req_bytes.Add(data_len[i]);
            }

            if (arg.DataType == DataType.LIST)
            {
                foreach (var node in arg.SubNode)
                {
                    req_bytes.AddRange(MessageToBytes(node));
                }
            }
            else
            {
                req_bytes.AddRange(arg.Datas);
            }
            return req_bytes.ToArray();
        }

        private Message GetMessage(byte[] bytes)
        {
            Message msg = new Message();
            byte b = bytes[0];
            byte data_type = (byte)(b & 0b11111100);
            DataType dtType = (DataType)data_type;
            msg.DataType = dtType;

            byte byte_len = (byte)(b & 0b00000011);
            int index = 1;
            byte[] data_len = bytes.Skip(index).Take(byte_len).ToArray();
            int count = 0;
            for (int i = 0; i < data_len.Length; i++)
            {
                count += data_len[i] * (int)Math.Pow(256, i);
            }

            msg.Length = count;
            index += byte_len;

            if (dtType == DataType.LIST)
            {
                GetMessage(bytes.Skip(index).ToArray(), msg, count);
            }
            else
            {
                msg.Datas = bytes.Skip(index).ToArray();
            }
            return msg;
        }

        private void GetMessage(byte[] bytes, Message msg_parent, int count)
        {
            int index = 0;
            while (count-- > 0)
            {
                Message msg = new Message();
                byte b = bytes[index];
                byte data_type = (byte)(b & 0b11111100);
                byte byte_len = (byte)(b & 0b00000011);

                DataType dtType = (DataType)data_type;
                msg.DataType = dtType;
                index++;
                byte[] data_len = bytes.Skip(index).Take(byte_len).ToArray();
                int len = 0;
                for (int i = 0; i < data_len.Length; i++)
                {
                    len += data_len[i] * (int)Math.Pow(256, i);
                }
                msg.Length = len;
                index += byte_len;

                if (dtType == DataType.LIST)
                {
                    if (len > 0)
                    {
                        GetMessage(bytes.Skip(index).ToArray(), msg, len);
                    }
                }
                else
                {
                    msg.Datas = bytes.Skip(index).Take(len).ToArray();
                    index += len;
                }
                msg_parent.SubNode.Add(msg);
            }
        }

        public Message S1F1()
        {
            return this.Request(_deviceId, 0x81, 1, null);
        }

        public Message S1F3(UInt32 SVID)
        {
            Message message = new Message();
            message.DataType = DataType.LIST;
            message.Length = 1;

            byte[] datas = BitConverter.GetBytes(SVID);
            if(BitConverter.IsLittleEndian) 
                Array.Reverse(datas);
            message.SubNode.Add(new Message()
            {
                DataType = DataType.INT_4,
                Length = datas.Length,
                Datas = datas
            });
            return this.Request(_deviceId,0x81,3,message);
        }

        public Message S1F13(string MDLN, string SOFTREV)
        {
            Message message = new Message();
            message.DataType = DataType.LIST;
            message.Length = 2;

            byte[] mdln_bytes = Encoding.UTF8.GetBytes(MDLN);
            byte[] softrev_bytes = Encoding.UTF8.GetBytes(SOFTREV);
            message.SubNode.Add(new Message()
            {
                DataType = DataType.ASCII,
                Length = mdln_bytes.Length,
                Datas = mdln_bytes,
            });
            message.SubNode.Add(new Message()
            {
                DataType = DataType.ASCII,
                Length = softrev_bytes.Length,
                Datas = softrev_bytes
            });

            return this.Request(_deviceId,0x81,13,message);
        }

        public Message S1F15()
        {
            return this.Request(_deviceId, 0x81, 15, null);
        }

        public Message S1F17()
        {
            return this.Request(_deviceId, 0x81, 17, null);
        }
    }
}
